//
//  RSNumber.m
//  RSMath
//
//  Copyright (c) 2007-2009 Raphael Sobik. All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions are met:
//  1. Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//  2. Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//  3. The name of the author may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
//  THIS SOFTWARE IS PROVIDED BY Raphael Sobik "AS IS" AND ANY EXPRESS
//  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
//  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
//  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
//  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
//  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
//  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
//  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
//  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
//  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//

#import "RSNumber.h"

#import "RSMath.h"

@implementation RSNumber 

@synthesize bignum;
@dynamic string;

#pragma mark Initialisation Methods

- (id)init
{
	if(![super init])
    {
        return nil;
    }

	bignum = BN_new();
	if(bignum == NULL)
    {
        return nil;
    }
	BN_zero(bignum);
	
	
	ctx = BN_CTX_new();
	if(ctx == NULL)
    {
        return nil;
    }
	
	return self;
}

- (id)initWithInteger:(NSInteger)integer
{
	if(![self init])
    {
        return nil;
    }
	
	if(integer < 0)
    {
		integer *= -1;
		BN_set_word(bignum, integer);
		BN_sub_word(bignum, integer);
		BN_sub_word(bignum, integer);
	}
    else
    {
		BN_set_word(bignum, integer);
	}
	
	return self;
}

- (id)initWithUnsignedInteger:(NSUInteger)integer
{
    NSNumber *num = [NSNumber numberWithUnsignedInteger:integer];
    return [self initWithString:[num stringValue]];
}

- (id)initWithString:(NSString *)string
{
	if(![self init])
    {
        return nil;
    }
	BIGNUM *temp = [self bignum];
	BN_dec2bn(&temp, [string cStringUsingEncoding:NSASCIIStringEncoding]);
	return self;
}

- (void)dealloc
{
	BN_free(bignum);
	BN_CTX_free(ctx);
	[super dealloc];
}

- (void)finalize
{
	BN_free(bignum);
	BN_CTX_free(ctx);
	[super dealloc];
}

- (id)copyWithZone:(NSZone *)zone
{
    id new = [[[self class] alloc] init];
	//RSNumber *new = [RSNumber number];
	BN_copy([new bignum], [self bignum]);
	return new;
}

+ (id)numberWithInteger:(NSInteger)integer
{
	return [[[RSNumber alloc] initWithInteger:integer] autorelease];
}

+ (id)numberWithUnsignedInteger:(NSUInteger)integer
{
    return [[[RSNumber alloc] initWithUnsignedInteger:integer] autorelease];
}

+ (id)numberWithString:(NSString *)string
{
	return [[[RSNumber alloc] initWithString:string] autorelease];
}

+ (id)number
{
	return [RSNumber numberWithInteger:0];
}

- (id)initWithCoder:(NSCoder *)coder
{
	return [self initWithString:[coder decodeObjectForKey:@"bignum"]];
}

- (void)encodeWithCoder:(NSCoder *)coder
{
	[coder encodeObject:[self string] forKey:@"bignum"];
}

#pragma mark Setter and Getter

- (NSString *)string
{
	return [NSString stringWithCString:BN_bn2dec([self bignum])];
}

- (NSString *)description
{
	return [self string];
}

#pragma mark Comparsion Methods

- (BOOL)isZero
{
	if(BN_is_zero([self bignum]))
    {
        return TRUE;
    }
	else
    {
        return FALSE;
    }
}

- (BOOL)isOne
{
	if(BN_is_one([self bignum]))
    {
        return TRUE;
    }
	else
    {
        return FALSE;
    }
}

- (BOOL)isPrime
{
	if(BN_is_prime([self bignum], 2, NULL, ctx, NULL))
    {
        return TRUE;
    }
	else
    {
        return FALSE;
    }
}

- (NSArray *)factorization
{
    NSArray *primes = [RSMath primesUpto:self];
    NSMutableArray *primeFactors = [NSMutableArray array];
    RSNumber *copy = [self copy];
    
    for (RSNumber *prime in primes)
    {
        while ([[copy mod:prime] isZero])
        {
            [primeFactors addObject:prime];
            copy = [copy divideWith:prime];
        }
        if ([copy isOne])
        {
            break;
        }
    }

    return [primeFactors copy];
}

- (BOOL)isNegative
{
	if([self isSmallerThan:[RSNumber numberWithInteger:0]])
    {
        return TRUE;
    }
	else
    {
        return FALSE;
    }
}

- (BOOL)isLargerThan:(RSNumber *)number
{
	if(BN_cmp([self bignum], [number bignum]) == 1)
    {
        return TRUE;
    }
	else
    {
        return FALSE;
    }
}

- (BOOL)isEqualToNumber:(RSNumber *)number
{
	if(BN_cmp([self bignum], [number bignum]) == 0)
    {
        return TRUE;
    }
	else
    {
        return FALSE;
    }
}

- (BOOL)isEqualTo:(id)object
{
	if([object isKindOfClass:[RSNumber class]])
    {
		return [self isEqualToNumber:(RSNumber *)object];
    }
	else
    {
		return [super isEqualTo:object];
    }
}

- (BOOL)isSmallerThan:(RSNumber *)number
{
	if(BN_cmp([self bignum], [number bignum]) == -1)
    {
        return TRUE;
    }
	else
    {
        return FALSE;
    }
}

#pragma mark Arithmetic Operations

- (RSNumber *)mod:(RSNumber *)mod
{
	RSNumber *result = [RSNumber number];
	BN_mod([result bignum], [self bignum], [mod bignum], ctx);
	return result;
}

- (RSNumber *)inverseMod:(RSNumber *)mod
{
	RSNumber *result = [RSNumber number];
	BN_mod_inverse([result bignum], [self bignum], [mod bignum], ctx);
	return result;
}

- (RSNumber *)add:(RSNumber *)otherNumber
{
	RSNumber *result = [[RSNumber alloc] init];
	BN_add([result bignum], [self bignum], [otherNumber bignum]);
	return result;
}

- (RSNumber *)add:(RSNumber *)otherNumber mod:(RSNumber *)mod
{
	RSNumber *result = [[RSNumber alloc] init];
	BN_mod_add([result bignum], [self bignum], [otherNumber bignum], [mod bignum], ctx);
	return result;
}

- (RSNumber *)subtract:(RSNumber *) otherNumber
{
	RSNumber *result = [RSNumber number];
	BN_sub([result bignum], [self bignum], [otherNumber bignum]);
	return result;
}

- (RSNumber *)subtract:(RSNumber *) otherNumber mod:(RSNumber *)mod
{
	RSNumber *result = [RSNumber number];
	BN_mod_sub([result bignum], [self bignum], [otherNumber bignum], [mod bignum], ctx);
	return result;
}

- (RSNumber *)multiplyWith:(RSNumber *) otherNumber
{
	RSNumber *result = [RSNumber number];
	BN_mul([result bignum], [self bignum], [otherNumber bignum], ctx);
	return result;
}

- (RSNumber *)multiplyWith:(RSNumber *) otherNumber mod:(RSNumber *)mod
{
	RSNumber *result = [RSNumber number];
	BN_mod_mul([result bignum], [self bignum], [otherNumber bignum], [mod bignum], ctx);
	return result;
}

- (RSNumber *)divideWith:(RSNumber *) otherNumber
{
	RSNumber *result, *remainder;
	result = [RSNumber number];
	remainder = [RSNumber number];
	BN_div([result bignum], [remainder bignum], [self bignum], [otherNumber bignum], ctx);
	return result;
}

- (RSNumber *)toThePowerOf:(RSNumber *) otherNumber
{
	RSNumber *result = [RSNumber number];
	BN_exp([result bignum], [self bignum], [otherNumber bignum], ctx);
	return result;
}

- (RSNumber *)toThePowerOf:(RSNumber *) otherNumber mod:(RSNumber *)mod
{
	RSNumber *result = [RSNumber number];
	BN_mod_exp([result bignum], [self bignum], [otherNumber bignum], [mod bignum], ctx);
	return result;
}

@end
